package fun.sssdnsy.util;

import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import static fun.sssdnsy.util.FileUtil.*;

public class FaceRecognitionUtil {
    public static final Logger log = LoggerFactory.getLogger(FaceRecognitionUtil.class);

    public static final String PACKAGE_PATH = "/fun/sssdnsy/";
    public static final String CASCADE_PATH = PACKAGE_PATH + "config/haarcascade_frontalface_alt2.xml";
    public static final String FACE_DATA_PATH = PACKAGE_PATH + "data/";
    public static final String LIB_PATH = PACKAGE_PATH + "lib/";
    public static final int RECOGNIZED_DISPLAY_MS = 1000;
    public static final int TIMEOUT_SECONDS = 30;
    public static final double MIN_FACE_QUALITY = 0.7;

    private static final CascadeClassifier cascadeClassifier;

    static {
        try {
            loadOpenCV();
            cascadeClassifier = loadCascade(CASCADE_PATH);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    public static void registerFaceAsync(Stage ownerStage, Consumer<Boolean> callback) {
        AtomicBoolean stopSignal = new AtomicBoolean(false);

        CompletableFuture.runAsync(() -> {
            try {
                VideoCapture camera = new VideoCapture();
                if (!camera.open(0)) {
                    log.error("摄像头无法打开");
                    Platform.runLater(() -> {
                        new Alert(Alert.AlertType.ERROR, "无法打开摄像头").show();
                        callback.accept(false);
                    });
                    return;
                }

                Mat frame = new Mat();
                Mat rgb = new Mat();
                List<Mat> candidates = new ArrayList<>();

                AtomicReference<Stage> cameraStageRef = new AtomicReference<>();
                ImageView imageView = new ImageView();

                Platform.runLater(() -> {
                    Stage cameraStage = new Stage();
                    cameraStageRef.set(cameraStage);
                    VBox root = new VBox(imageView);
                    Scene scene = new Scene(root, 640, 480);
                    cameraStage.setScene(scene);
                    cameraStage.setTitle("请正视摄像头进行采集...");
                    cameraStage.setOnCloseRequest(e -> {
                        stopSignal.set(true);
                        new Alert(Alert.AlertType.WARNING, "已取消人脸识别", ButtonType.OK).show();
                    });
                    cameraStage.initOwner(ownerStage);
                    cameraStage.show();
                });

                long start = System.currentTimeMillis();
                while (!stopSignal.get() && System.currentTimeMillis() - start < TIMEOUT_SECONDS * 1000) {
                    if (!camera.read(frame)) continue;

                    Imgproc.cvtColor(frame, rgb, Imgproc.COLOR_BGR2RGB);
                    Mat gray = preprocessFrame(frame);
                    Rect faceRect = detectBestFace(gray, cascadeClassifier);

                    if (faceRect != null) {
                        Imgproc.rectangle(rgb, faceRect, new Scalar(255, 0, 0), 2);
                        Mat face = processFaceRegion(gray, faceRect);
                        double quality = calculateFaceQuality(face);
                        if (quality >= MIN_FACE_QUALITY) {
                            candidates.add(face);
                            Imgproc.putText(rgb, "Verifying...", new Point(30, 40), Imgproc.FONT_HERSHEY_SIMPLEX, 1.2, new Scalar(0, 255, 255), 2);
                        }
                    }

                    Image fxImage = matToFxImage(rgb);
                    Platform.runLater(() -> imageView.setImage(fxImage));

                    if (candidates.size() >= 5) break;
                }

                Mat best = selectBestFace(candidates);
                boolean success = best != null;

                Platform.runLater(() -> {
                    Stage cameraStage = cameraStageRef.get();
                    if (cameraStage != null) cameraStage.close();
                    if (success) {
                        new Alert(Alert.AlertType.INFORMATION, "采集成功", ButtonType.OK).showAndWait();
                    } else {
                        new Alert(Alert.AlertType.WARNING, "采集失败", ButtonType.OK).showAndWait();
                    }
                    callback.accept(success);
                });
                // 关闭时清除资源
                Platform.runLater(() -> {
                    Stage cameraStage = cameraStageRef.get();
                    if (cameraStage != null) {
                        cameraStage.close();
                        cameraStageRef.set(null); // 帮助GC回收
                    }
                });
            } catch (Exception e) {
                log.error("人脸采集异常", e);
                Platform.runLater(() -> callback.accept(false));
            }
        }, ThreadPoolUtil.FACE_POOL);
    }

    public static void verifyWithCameraAsync(Stage ownerStage, Consumer<Boolean> callback) {
        AtomicBoolean stopSignal = new AtomicBoolean(false);

        CompletableFuture.runAsync(() -> {
            try {
                VideoCapture camera = new VideoCapture();
                if (!camera.open(0)) {
                    log.error("摄像头无法打开");
                    Platform.runLater(() -> {
                        new Alert(Alert.AlertType.ERROR, "无法打开摄像头").show();
                        callback.accept(false);
                    });
                    return;
                }

                Mat frame = new Mat();
                Mat rgb = new Mat();
                AtomicBoolean recognized = new AtomicBoolean(false);
                long lastRecognized = 0;

                ImageView imageView = new ImageView();

                AtomicReference<Stage> cameraStage = new AtomicReference<>();
                Platform.runLater(() -> {
                    cameraStage.set(new Stage());
                    VBox root = new VBox(imageView);
                    Scene scene = new Scene(root, 640, 480);
                    cameraStage.get().setScene(scene);
                    cameraStage.get().setTitle("人脸识别中...");
                    cameraStage.get().setOnCloseRequest(e -> stopSignal.set(true));
                    cameraStage.get().initOwner(ownerStage);
                    cameraStage.get().show();
                });

                long start = System.currentTimeMillis();
                while (!stopSignal.get() && System.currentTimeMillis() - start < TIMEOUT_SECONDS * 1000) {
                    if (!camera.read(frame)) continue;

                    Imgproc.cvtColor(frame, rgb, Imgproc.COLOR_BGR2RGB);
                    Mat gray = preprocessFrame(frame);
                    Rect faceRect = detectBestFace(gray, cascadeClassifier);

                    if (faceRect != null) {
                        Imgproc.rectangle(rgb, faceRect, new Scalar(0, 0, 255), 2);
                        Mat face = processFaceRegion(gray, faceRect);

                        if (isFaceRegistered() && verifyUser(face)) {
                            recognized.set(true);
                            lastRecognized = System.currentTimeMillis();
                            Imgproc.putText(rgb, "OK", new Point(30, 40), Imgproc.FONT_HERSHEY_SIMPLEX, 1.2, new Scalar(0, 255, 0), 2);

                        }
                    }

                    Image fxImage = matToFxImage(rgb);
                    Platform.runLater(() -> imageView.setImage(fxImage));

                    if (recognized.get() && System.currentTimeMillis() - lastRecognized > RECOGNIZED_DISPLAY_MS) {
                        camera.release();
                        break;
                    }
                }

                Platform.runLater(() -> {
                    cameraStage.get().close();
                    if (!recognized.get()) {
                        log.warn("识别超时或失败");
                        new Alert(Alert.AlertType.WARNING, "识别超时或失败", ButtonType.OK).show();
                    }
                    callback.accept(recognized.get());
                    camera.release();
                });

            } catch (Exception ex) {
                log.error("识别异常", ex);
                Platform.runLater(() -> callback.accept(false));
            }
        }, ThreadPoolUtil.FACE_POOL);
    }

    public static boolean verifyUser(Mat faceToVerify) {
        if (!isFaceRegistered()) {
            log.warn("未注册人脸");
            return false;
        }

        Mat registeredFace = Imgcodecs.imread(getFaceImageFile(FACE_DATA_PATH), Imgcodecs.IMREAD_GRAYSCALE);
        if (registeredFace.empty() || faceToVerify == null) return false;

        Mat resized = new Mat();
        Imgproc.resize(faceToVerify, resized, registeredFace.size());

        Mat diff = new Mat();
        Core.absdiff(resized, registeredFace, diff);
        diff.convertTo(diff, CvType.CV_32F);
        Core.multiply(diff, diff, diff);
        Scalar sumSq = Core.sumElems(diff);
        double mse = sumSq.val[0] / resized.total();

        log.info("当前MSE: {}", mse);
        return mse < 1000;
    }

    public static boolean isFaceRegistered() {
        return hasCapturePngFile(FACE_DATA_PATH);
    }

    public static Mat preprocessFrame(Mat frame) {
        Mat gray = new Mat();
        Imgproc.cvtColor(frame, gray, Imgproc.COLOR_BGR2GRAY);
        Imgproc.equalizeHist(gray, gray);
        return gray;
    }

    public static Rect detectBestFace(Mat frame, CascadeClassifier detector) {
        MatOfRect detections = new MatOfRect();
        detector.detectMultiScale(frame, detections, 1.1, 3, 0, new Size(100, 100), new Size());
        return detections.toArray().length > 0 ? findLargestFace(detections.toArray()) : null;
    }

    public static Rect findLargestFace(Rect[] faces) {
        Rect largest = faces[0];
        for (Rect face : faces) {
            if (face.area() > largest.area()) {
                largest = face;
            }
        }
        return largest;
    }

    public static Mat processFaceRegion(Mat frame, Rect rect) {
        Mat face = new Mat(frame, rect);
        Mat resized = new Mat();
        Imgproc.resize(face, resized, new Size(200, 200));
        return resized;
    }

    public static double calculateFaceQuality(Mat face) {
        Mat laplacian = new Mat();
        Imgproc.Laplacian(face, laplacian, CvType.CV_64F);
        MatOfDouble stddev = new MatOfDouble();
        Core.meanStdDev(laplacian, new MatOfDouble(), stddev);
        return stddev.get(0, 0)[0];
    }

    public static Mat selectBestFace(List<Mat> faces) {
        if (faces == null || faces.isEmpty()) return null;
        Mat best = faces.get(0);
        double maxQuality = 0;
        for (Mat face : faces) {
            double quality = calculateFaceQuality(face);
            if (quality > maxQuality) {
                maxQuality = quality;
                best = face;
            }
        }
        String faceImageFile = getFaceImageFile(FACE_DATA_PATH);
        Imgcodecs.imwrite(faceImageFile, best);
        log.info("已保存最佳人脸至: {}", faceImageFile);
        return best;
    }

    public static CascadeClassifier loadCascade(String cascadePath) throws URISyntaxException {
        File cascadeFile = new File(getResourcePath(cascadePath));
        if (!cascadeFile.exists()) {
            log.error("级联分类器文件不存在: {}", cascadePath);
            return null;
        }
        CascadeClassifier detector = new CascadeClassifier(cascadeFile.getAbsolutePath());
        if (detector.empty()) {
            log.error("无法加载级联分类器: {}", cascadePath);
            return null;
        }
        return detector;
    }

    public static void loadOpenCV() {
        String os = System.getProperty("os.name").toLowerCase();
        String libName;
        if (os.contains("win")) {
            libName = Core.NATIVE_LIBRARY_NAME + ".dll";
        } else if (os.contains("nix") || os.contains("nux")) {
            libName = Core.NATIVE_LIBRARY_NAME + ".so";
        } else if (os.contains("mac")) {
            libName = Core.NATIVE_LIBRARY_NAME + ".dylib";
        } else {
            throw new UnsupportedOperationException("不支持的操作系统: " + os);
        }

        String resourcePath = LIB_PATH + libName;
        File libFile = new File(getResourcePath(resourcePath));
        if (!libFile.exists()) throw new RuntimeException("OpenCV 文件缺失: " + libFile.getAbsolutePath());
        System.load(libFile.getAbsolutePath());
        log.info("OpenCV 加载成功: {}", libFile.getAbsolutePath());
    }

    private static Image matToFxImage(Mat mat) {
        try {
            Mat rgbMat = new Mat();
            Imgproc.cvtColor(mat, rgbMat, Imgproc.COLOR_BGR2RGB);
            MatOfByte buffer = new MatOfByte();
            Imgcodecs.imencode(".png", rgbMat, buffer);
            return new Image(new ByteArrayInputStream(buffer.toArray()));
        } catch (Exception e) {
            log.error("图像转换失败", e);
            return null;
        }
    }
}
